/*******************************************************************************
 * Copyright (c) 2009 Kevin Bierhoff and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Kevin Bierhoff - initial API and implementation
 *******************************************************************************/
package com.googlecode.twelfdevtools.console;

import java.util.LinkedList;
import java.util.List;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;

/**
 * Encapsulates a Twelf error message.
 * @author Kevin Bierhoff
 */
public class TwelfErrorMessage {
	
	static Pattern TWELF_ERROR = 
		Pattern.compile("(.*):(\\d*)\\.(\\d*)-(\\d*)\\.(\\d*) Error:");
	
	/**
	 * Parses a Twelf error message from the given region in the given
	 * document.
	 * @param doc Document that contains the error message to parse.
	 * @param offset Offset of the beginning of the error message in <code>doc</code>.
	 * @param length Length of the error message to parse in <code>doc</code>.
	 * @return The parsed error message or <code>null</code>
	 * if the given region was not a Twelf message.
	 * @throws BadLocationException If the given region is invalid.
	 * @throws NumberFormatException If the start line couldn't be parsed
	 * (shouldn't happen).
	 * @throws ArrayIndexOutOfBoundsException If the error message contained
	 * no information about the source file or the start line (shouldn't happen).
	 */
	public static TwelfErrorMessage parseError(IDocument doc, int offset, int length) throws BadLocationException {
		String errorMsg = doc.get(offset, length);
		
		Matcher m = TWELF_ERROR.matcher(errorMsg);
		if(m.matches()) {
			// required attributes
			String fileName = m.group(1); // groups are 1-based
			int startRow = Integer.parseInt(m.group(2));
			
			// optional attributes
			int startCol = parseOptionalIntGroup(m, 3);
			int endRow = parseOptionalIntGroup(m, 4);
			int endCol = parseOptionalIntGroup(m, 5);
			List<String> errorDetails = errorDetailLookahead(doc, offset);
			
			// create message
			return new TwelfErrorMessage(fileName, startRow, startCol, endRow, endCol, errorDetails);
		}
		else {
			// argument error: given location wasn't actually a Twelf error
			return null;
		}
	}

	/**
	 * Attempts to parse the given group of the given match result as 
	 * an integer.
	 * @tag usage.parameter: Must provide a valid match.
	 * @param match A valid match result.
	 * @param groupIndex A 1-based group index in the match.
	 * @return The parsed integer or -1 if the given group was unknown
	 * or couldn't be parsed as an integer.
	 * @see MatchResult#group(int)
	 * @see Integer#parseInt(String)
	 */
	private static int parseOptionalIntGroup(MatchResult match, int groupIndex) {
		int result = -1;
		try {
			result = Integer.parseInt(match.group(groupIndex));
		} 
		catch (NumberFormatException e) {
			System.err.println("Error parsing group: " + groupIndex);
			e.printStackTrace();
		}
		catch (ArrayIndexOutOfBoundsException e) {
			System.err.println("Couldn't find group: " + groupIndex);
			e.printStackTrace();
		}
		return result;
	}

	private static List<String> errorDetailLookahead(IDocument doc, int offset) {
		List<String> result = new LinkedList<String>();
		try {
			int row = doc.getLineOfOffset(offset);
			int rowOffset = doc.getLineOffset(row);
			int rowLength = doc.getLineLength(row);
			while(rowOffset + rowLength + 1 < doc.getLength()) {
				++row;
				rowOffset = doc.getLineOffset(row);
				rowLength = doc.getLineLength(row);
				
				String line = doc.get(rowOffset, rowLength);
				// unfortunately this contains the newline
				line = line.replaceAll("\n", "");
				
				// does this line stil belong to the error? 
				if(line.startsWith("%%"))
					break; // %% OK %% or %% ABORT %%
				if(line.startsWith("["))
					break; // [Opening ...] or [Closing ...]
				if(line.startsWith("Twelf"))
					break; // Twelf server says HI (shouldn't occur)
				if(line.lastIndexOf("Error:") >= 0 && TWELF_ERROR.matcher(line).find())
					break; // next error message
				
				result.add(line);
			}
		} 
		catch (BadLocationException e) {
			System.err.println("Problem retrieving error details from document");
			e.printStackTrace();
		}
		return result;
	}

	private String fileName;
	private int startRow;
	private int startCol;
	private int endRow;
	private int endCol;
	private String details;

	/**
	 * Creates a Twelf error message with the given attributes.
	 * @param fileName Must be a non-empty string.
	 * @param startRow Must be a positive number.
	 * @param startCol Optional: Pass -1 when unknown.
	 * @param endRow Optional: Pass -1 when unknown.
	 * @param endCol Optional: Pass -1 when unknown.
	 * @param errorDetails Must be non-<code>null</code>.
	 */
	private TwelfErrorMessage(String fileName, int startRow, int startCol,
			int endRow, int endCol, List<String> errorDetails) {
		assert fileName != null && fileName.length() > 0;
		assert startRow >= 1;
		assert errorDetails != null;
		this.fileName = fileName;
		this.startRow = startRow;
		this.startCol = startCol;
		this.endRow = endRow;
		this.endCol = endCol;
		this.details = processDetails(errorDetails);
	}

	private static String processDetails(List<String> errorDetails) {
		if(errorDetails.isEmpty()) return "";
		StringBuilder result = new StringBuilder();
		for(String line : errorDetails) {
			line = line.trim();
			if(Pattern.matches("\\d* errors found", line)) {
				return null;
			}
			if(result.length() > 0)
				result.append(". ");
			result.append(line);
		}
		return result.toString();
	}
	
	/**
	 * Returns the file name.
	 * @return The file name, not <code>null</code>.
	 */
	public String getFileName() {
		return fileName;
	}

	/**
	 * Returns the start row.
	 * @return The start row, a strictly positive number.
	 */
	public int getStartRow() {
		return startRow;
	}

	/**
	 * Returns the start column (optional attribute).
	 * @return the start column or -1 if unknown.
	 */
	public int getStartCol() {
		return startCol;
	}

	/**
	 * Returns the end row (optional attribute).
	 * @return the end row or -1 if unknown.
	 */
	public int getEndRow() {
		return endRow;
	}

	/**
	 * Returns the end column (optional attribute).
	 * @return the end column or -1 if unknown.
	 */
	public int getEndCol() {
		return endCol;
	}
	
	/**
	 * Indicates whether this is a Twelf "summary" error.
	 * A summary error is a message with the details
	 * "X errors found".
	 * @return <code>true</code> if this a summary error, 
	 * <code>false</code> otherwise.
	 */
	public boolean isSummaryError() {
		return details == null;
	}

	/**
	 * Returns the (possibly empty) error details.
	 * @return the error details, never <code>null</code>.
	 */
	public String getDetails() {
		return details == null ? "" : details;
	}
	
	public String getLocationString() {
		if(startRow >= 0 && startCol >= 0 && endRow >= 0 && endCol >= 0) {
			return "" + startRow + '.' + startCol + '-' + endRow + '.' + endCol;
		}
		else
			return null;
	}

}
